/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package uloha4.view;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import uloha4.entities.Component;
import uloha4.entities.ComponentList;
import uloha4.entities.Node;
import uloha4.main.MyMath;

/**
 *
 * @author Tomique
 */
public class MagicCanvas extends JPanel {

    private static MagicCanvas instance;
    private double translateX;                  // posun v ose x
    private double translateY;                  // posun v ose y
    private double scale;                       // meritko zvetseni / zmenseni
    private ComponentList components;
    private Graphics2D g;
    private final int nodeSize = 10;            // vychozi velikost vykreslovaneho nodu   
    private boolean antialiasFlag = false;      // flag, zda se bude vykreslovat s antialiasingem
    private boolean nodenameFlag = false;       // flag, zda budou vykreslena jmena nodu
    private boolean forceOrientFlag = false;    // flag, zda dojde k vynucene orientaci hran
    private boolean visualizeLonelyNodeFlag = false;    // flag, zda dojde k vizualizaci osamocenych uzlu
    private boolean nodeDegreeFlag = false;     // flag, zda dojde k vizualizaci stupne uzlu

    private MagicCanvas() {
        translateX = 0.0;
        translateY = 0.0;
        scale = 1.0;
        this.components = ComponentList.getInstance();
    }

    /**
     * Tato trida je instanciovana na vice mistech a je nezbytne nutne, aby
     * vsechny controllery pracovaly prave s jednou instanci. Proto je tato
     * trida koncipovana jako singleton.
     *
     * @return Samojedina instance vykreslovaciho canvasu
     */
    public static MagicCanvas getInstance() {
        if (instance == null) {
            instance = new MagicCanvas();
        }
        return instance;
    }

    public void setScale(double scale) {
        this.scale = scale;
    }

    public void setTranslateX(double translateX) {
        this.translateX = translateX;
    }

    public void setTranslateY(double translateY) {
        this.translateY = translateY;
    }

    public double getScale() {
        return scale;
    }

    public double getTranslateX() {
        return translateX;
    }

    public double getTranslateY() {
        return translateY;
    }

    public void addScale(double scale) {
        this.scale += scale;
    }

    public void addTranslateX(double translateX) {
        this.translateX += translateX;
    }

    public void addTranslateY(double translateY) {
        this.translateY += translateY;
    }

    public void setAntialiasFlag(boolean antialiasFlag) {
        this.antialiasFlag = antialiasFlag;
    }

    public void setNodenameFlag(boolean nodenameFlag) {
        this.nodenameFlag = nodenameFlag;
    }

    public void setForceOrientFlag(boolean forceOrientFlag) {
        this.forceOrientFlag = forceOrientFlag;
    }

    public void setVisualizeLonelyNodeFlag(boolean visualizeLonelyNodeFlag) {
        this.visualizeLonelyNodeFlag = visualizeLonelyNodeFlag;
    }

    public void setVisualizeNodeDegree(boolean nodeDegreeFlag) {
        this.nodeDegreeFlag = nodeDegreeFlag;
    }

    @Override
    public void paint(Graphics g1) {

        g = (Graphics2D) g1;

        // vykresleni gradientu na pozadi
        ShapeFactory.paintGradient(this, g);

        // potrebuji stred vykreslovaci plochy pro konstrukci "ubezniku"
        Dimension d = this.getSize();
        int centerW = d.width / 2;
        int centerH = d.height / 2;

        AffineTransform tx = new AffineTransform();
        // diky vycentrovani pres centerW a centerH dojde ke vzniku "ubezniku" vzdy ve stredu obrazu
        tx.translate(centerW + translateX, centerH + translateY);
        tx.scale(scale, scale);
        g.setTransform(tx);

        // rozhoduji se, zda vykreslovat s antialiasingem, ci nikoliv
        if (antialiasFlag) {
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        } else {
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        }

        paintComponents();

    }

    private void paintComponents() {
        if (components == null) {
            return;
        }

        g.setColor(Color.BLACK);

        for (Component<Node> comp : components) {
            if (comp.isVisible()) { // viditelnost jednotlivych komponent
                if (!visualizeLonelyNodeFlag && comp.size() == 1) {
                    continue;
                }
                Node highlightNode;
                HashSet<Node> hash = new HashSet<>();

                for (Node node : comp) {
                    if (node.isHighlighted()) {
                        highlightNode = node;
                        for (Node neighbour : node.getNeighbours()) {
                            hash.add(neighbour);
                        }
                        break;
                    }
                }
                visualizeGraph(comp, hash, Color.DARK_GRAY, Color.BLACK, Color.YELLOW, Color.BLUE, Color.RED);
            }
        }
    }

    private void visualizeGraph(Component<Node> comp, HashSet<Node> hash, Color edgeColor, Color nodeColor, Color highlightColor, Color coronaColor, Color textColor) {


        for (Node node : comp) {

            float nodeScale = nodeDegreeFlag ? node.getScale() : 1.0f;
            int size = Math.round((float) nodeSize * nodeScale);
            float sizeF = nodeSize * nodeScale;
            int sizeHalf = Math.round((float) nodeSize * nodeScale / 2.0f);

            // souradnice pocatecniho bodu
            int x0 = node.pos.x;
            int y0 = node.pos.y;
            // ke kazdemu sousedovi vykreslim sipku
            if (node.isHighlighted()) {
                for (Node neighbour : node.getNeighbours()) {
                    // souradnice koncoveho bodu
                    int x1 = neighbour.pos.x;
                    int y1 = neighbour.pos.y;
                    g.setColor(highlightColor);
                    ShapeFactory.drawArrow(g, x0, y0, x1, y1, 8, sizeF, forceOrientFlag);

                }
            } else {
                for (Node neighbour : node.getDirectedNeighbours()) {
                    // souradnice koncoveho bodu
                    int x1 = neighbour.pos.x;
                    int y1 = neighbour.pos.y;
                    g.setColor(edgeColor);
                    if (!neighbour.isHighlighted()) {
                        ShapeFactory.drawArrow(g, x0, y0, x1, y1, 8, sizeF, forceOrientFlag);
                    }
                }
            }

            g.setColor(nodeColor);
            // zvyraznim oznaceny uzel
            if (node.isHighlighted()) {
                g.setColor(coronaColor);
                g.fillOval(node.pos.x - size, node.pos.y - size, size * 2, size * 2);
                g.setColor(highlightColor);
            }
            if (hash.contains(node)) {
                if (nodenameFlag) {
                    g.setColor(Color.MAGENTA);
                    Font font = new Font("Arial", Font.PLAIN, size);
                    g.setFont(font);
                    g.drawString(node.getAuthorName(), node.pos.x + sizeHalf, node.pos.y);
                }
                g.setColor(highlightColor);
            }
            g.fillOval(node.pos.x - sizeHalf, node.pos.y - sizeHalf, size, size);

            if (nodenameFlag) {
                g.setColor(textColor);
                if (node.isHighlighted()) {
                    g.setColor(Color.RED);
                    Font font = new Font("Arial", Font.PLAIN, size);
                    g.setFont(font);
                    g.drawString(node.getAuthorName(), node.pos.x + sizeHalf, node.pos.y);
                }
            }
        }
    }

    /**
     * Iteruje pres vsechny existujici uzly a vybere ten, ktery se nachazi pod
     * kurzorem mysi. Konkretne je na zasazenem uzlu nastavena promenna
     * "highlighted". Vsechny ostatni nezasazene uzly maji tuto promennou
     * nastavenou na false.
     */
    public void markNode() {
        // <editor-fold defaultstate="collapsed" desc="markNode() implementation">
        // potrebuji ziskat inverzni transformaci, abych se dostal k uzlu, 
        // na ktery uzivatel kliknul
        AffineTransform at = g.getTransform();
        Point2D p = getMousePosition();
        // pomoci inverzni transformace ziskam puvodni souradnice kurzoru mysi
        try {
            at.inverseTransform(p, p);
        } catch (NoninvertibleTransformException ex) {
            Logger.getLogger(MagicCanvas.class.getName()).log(Level.SEVERE, null, ex);
        }

        // oznacim pouze prvni zasazeny objekt
        boolean flag = false;

        // projdu vsechny uzly a zjistim, ktereho se to tyka
        for (Component<Node> comp : components) {
            if (comp.isVisible()) {
                for (Node node : comp) {
                    if (!flag && MyMath.intersectsNode(node, p, nodeSize)) {
                        node.setHighlighted(true);
                        flag = true;
                    } else {
                        node.setHighlighted(false);
                    }
                }
            }
        }
        // </editor-fold>
    }
}
